/**
 * Matrix.java
 * 
 * @date: 2012-02-28
 * @author: Xiaoyu Guo (MUN# 200982155)
 * This file is part of the course project for COMP 6752, 
 * Application of Computer Graphics. All the file is written
 * by myself.
 */
package mun.cs6752.project.model;

/** Common Matrix operations 
 * @author Xiaoyu Guo
 */
public class Matrix {
    protected double[][] data;
    protected int width;
    protected int height;
    
    /** Construct a matrix with specified width and height, filled with 0s
     * @param width
     * @param height
     */
    public Matrix(int width, int height){
        if(width < 1 || height < 1){
            throw new IllegalArgumentException("width and height must >= 1");
        }
        
        this.width = width;
        this.height = height;
        this.data = new double[height][width];
        for(int i = 0; i < height; i++){
            this.data[i] = new double[width];
        }
    }
    
    /** Construct a matrix by well-formed data, stored by row
     * @param source
     */
    public Matrix(double[][] source){
        if(source == null){
            throw new IllegalArgumentException("source can not be null");
        }
        if(source.length < 1){
            throw new IllegalArgumentException("source can not be empty");
        }
        
        // check source has same width in each row
        int width = 0;
        for(int y = 0; y < source.length; y++){
            if(source[y] == null || source[y].length < 1){
                throw new IllegalArgumentException("row can not be empty");
            }
            
            if(width == 0){
                width = source[y].length;
            } else {
                if(width != source[y].length){
                    throw new IllegalArgumentException("each row must contain the same number of elements");
                }
            }
        }
        
        // now the source is good
        this.height = source.length;
        this.width = width;
        
        this.data = new double[this.height][];
        for(int y = 0; y < source.length; y++){
            this.data[y] = new double[this.width];
            for(int x = 0; x < this.width; x++){
                this.data[y][x] = source[y][x];
            }
        }
    }
    
    /** Gets the width of the matrix
     * @return how many columns does the matrix hold
     */
    public int getWidth(){
        return this.width;
    }
    
    /** Gets the height of the matrix
     * @return how many rows does the matrix hold
     */
    public int getHeight(){
        return this.height;
    }
    
    /** Get the specified element
     * @param row
     * @param column
     * @return
     */
    public double get(int row, int column){
        if(row < 0 || row >= width || column < 0 || column >= height){
            throw new IllegalArgumentException("illegal row or column");
        }
        return this.data[column][row];
    }
    
    /** Set value at specified position
     * @param row
     * @param column
     * @param value
     */
    public void set(int row, int column, double value){
        if(row < 0 || row >= width || column < 0 || column >= height){
            throw new IllegalArgumentException("illegal row or column");
        }
        this.data[column][row] = value;
    }
    
    /** Matrix multiplication
     * @param value
     * @return
     */
    public Matrix multiply(Matrix value){
        if(value == null){
            throw new IllegalArgumentException("can not multiply with null");
        }
        if(width != value.height){
            throw new IllegalArgumentException("matrix size do not match");
        }
        
        double[][] newData = new double[height][value.getWidth()];
        for(int y = 0; y < height; y++){
            newData[y] = new double[value.getWidth()];
        }
        
        for(int y = 0; y < height; y++){
            for(int x = 0; x < value.getWidth(); x++){
                double result = 0.0;
                
                for(int i = 0; i < width; i++){
                    result += get(x, i) * value.get(i, y);  
                }
                
                newData[y][x] = result;
            }
        }
        return new Matrix(newData);
    }
    
    /** Matrix multiply with a vector 
     * @param value
     * @return
     */
    public Vector multiply(Vector value){
        if(value == null){
            throw new IllegalArgumentException("can not multiply with null");
        }
        if(width != value.size()){
            throw new IllegalArgumentException("size does match");
        }
        
        Vector result = new Vector(value.size());
        
        for(int row = 0; row < value.size(); row++){
            double accu = 0.0;
            for(int column = 0; column < value.size(); column++){
                accu += get(row, column) * value.get(column);
            }
            
            result.set(row, accu);
        }
        
        return result;
    }
    
    /** Gets the transpose matrix
     * @return
     */
    public Matrix transpose(){
        Matrix result = new Matrix(height, width);
        
        for(int y = 0; y < height; y++){
            for(int x = 0; x < width; x++){
                result.set(y, x, get(x, y));
            }
        }
        
        return result;
    }
}
